package cn.lg.soar.common.util.signature;

import cn.lg.soar.common.algorithm.CryptoUtils;
import cn.lg.soar.common.util.asserts.InnerAssert;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.reflect.ReflectUtil;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 签名工具
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public interface SignatureUtils {

    /**
     * 根据对象生成签名
     * @param secret 密钥
     * @param data 要签名的数据
     * @param filterType 属性过滤方式（白名单 | 黑名单）
     * @param properties 过滤属性
     * @return
     */
    static String sign(String secret, Object data, FilterType filterType, String ...properties) {
        Set<String> propertiesSet = Arrays.stream(properties).collect(Collectors.toSet());
        if (filterType == null) {
            SignContain annotation = data.getClass().getAnnotation(SignContain.class);
            filterType = annotation == null ? FilterType.EXCLUDE : FilterType.CONTAIN;
        }
        boolean ignore = FilterType.EXCLUDE.equals(filterType);
        Map<String, Object> map = new HashMap<>();
        ReflectUtil.scanField(data.getClass(), field -> {
            String name = field.getName();
            // 忽略属性
            if (ignore) {
                // 属性黑名单
                if (propertiesSet.contains(name) || field.getAnnotation(SignExclude.class) != null) {
                    return;
                }
            } else {
                // 属性白名单
                if (!propertiesSet.contains(name) && field.getAnnotation(SignContain.class) == null) {
                    return;
                }
            }
            // 生成参数
            map.put(name, ReflectUtil.get(data, field));
        });
        return signByMap(secret, map);
    }

    /**
     * 生成签名（排除部分属性）
     * @param secret 密钥
     * @param data 要签名的数据
     * @return
     */
    static String sign(String secret, Object data) {
        return sign(secret, data, null);
    }

    /**
     * 根据map生成签名
     * @param secret 密钥
     * @param data 要签名的数据
     * @param filterType 属性过滤方式（白名单 | 黑名单）
     * @param properties 过滤属性
     * @return
     */
    static String signByMap(String secret, Map<String, Object> data, FilterType filterType, String ...properties) {
        InnerAssert.notEmpty(data, "data cannot empty");
        Set<String> propertiesSet = Arrays.stream(properties).collect(Collectors.toSet());
        boolean ignore = !FilterType.CONTAIN.equals(filterType);
        Set<Map.Entry<String, Object>> entries = data.entrySet();
        StringBuilder sb = new StringBuilder();
        entries.stream()
                .sorted(Utils.COMPARATOR)
                .forEach(x -> {
                    String key = x.getKey();
                    if (ignore) {
                        // 属性黑名单
                        if (propertiesSet.contains(key)) {
                            return;
                        }
                    } else {
                        // 属性白名单
                        if (!propertiesSet.contains(key)) {
                            return;
                        }
                    }
                    sb.append(key)
                            .append('=')
                            .append(x.getValue())
                            .append('&');
                });
        return signByString(secret, sb.substring(0, sb.length() - 1));
    }

    /**
     * 根据map生成签名
     * @param secret 密钥
     * @param data 要签名的数据
     * @return
     */
    static String signByMap(String secret, Map<String, Object> data) {
        InnerAssert.notEmpty(data, "data cannot empty");
        Set<Map.Entry<String, Object>> entries = data.entrySet();
        StringBuilder sb = new StringBuilder();
        entries.stream()
                .sorted(Utils.COMPARATOR)
                .forEach(x -> {
                    String key = x.getKey();
                    sb.append(key)
                            .append('=')
                            .append(x.getValue())
                            .append('&');
                });
        return signByString(secret, sb.substring(0, sb.length() - 1));
    }

    /**
     * 根据字符串生成签名
     * @param secret 密钥
     * @param data 要签名的数据
     * @return
     */
    static String signByString(String secret, String data) {
        return DataUtil.byteToHex(
                CryptoUtils.hmac256(secret.getBytes(), data.getBytes())
        );
    }

}
